<?
/**
 * @package    embryo
 * @subpackage libraries
 * @author     Alessandro Pelliciari
 * @version    SVN: $Id$
 */

/**
 * Db è la classe che gestisce la connessione con il database.
 *
 * @see Singleton
 */

class Db implements Singleton {

    //unica istanza
    private static $instance;

    private $connection;
    private $result_set; //current/last result set

    public $total_time_queries;
    public $total_queries;

    private $result_set_array;

    private $hostname;
    private $username;
    private $password;
    public  $database_name;
    private $port;
    private $ssl_mode;


    /**
     * Ritorna questo oggetto richiamando il costruttore se non ne esiste già un'instanza.
     *
     * @param Container $container  L'unico dependency injection container dell'applicazione
     * @param array     $settings   Array di parametri per inizializzare il database
     *
     * @return Db                   L'oggetto database
     *
     * @see __construct()           Per vedere quali parametri sono necessari
     * @see Singleton
     */
    public static function get_instance(Container $container, array $settings = array()) {
        if (!self::$instance) {
            self::$instance = new Db($settings["hostname"], $settings["username"], $settings["password"], $settings["database_name"], $settings["port"], $settings["ssl_mode"]);
        }
        return self::$instance;
    }


    /**
     * Costruttore - privato.
     *
     * @param string $hostname      Il nome host del server
     * @param string $username      L'username di accesso
     * @param string $password      La password di accesso
     * @param string $database_name Il nome del database
     * @param string $port          La porta di accesso (defaults: 3306 per mysql; 5432 per postgres)
     * @param boolean $ssl_mode     SSL attivo o meno
     */
    private function __construct($hostname = "", $username = "", $password = "", $database_name = "", $port = "3306", $ssl_mode = false) {

        $this->hostname = $hostname;
        $this->username = $username;
        $this->password = $password;
        $this->database_name = $database_name;

        //porta attualmente non utilizzata durante la connessione
        $this->port = $port ? $port : "3306";

        //ssl mode attualmente non utilizzato durante la connessione
        $this->ssl_mode = $ssl_mode ? $ssl_mode : false;

        $result_set_array = array();

        $this->connect();
    }

    /**
     * Connette al database e sistema gli encoding per una connessione totalmente UTF-8. - private
     */
    private function connect() {

        if(!$this->connection = mysql_connect($this->hostname, $this->username, $this->password))
            throw new Exception( mysql_error(), mysql_errno());

        if(!mysql_select_db($this->database_name, $this->connection))
            throw new Exception( mysql_error(), mysql_errno());


        // Query per sistemare le variabili del db:
        // php mette default a latin1, noi riportiamo il tutto in utf8
        $sql = "set character_set_client = 'utf8'";
        $this->query($sql);
        $sql = "set character_set_connection = 'utf8'";
        $this->query($sql);
        $sql = "set character_set_results = 'utf8'";
        $this->query($sql);
    }

    /**
     * Ritorna il numero di righe coinvolte nell'ultima query eseguita.
     *
     * @return int Numero di righe coinvolte nell'ultima query eseguita.
     */
    public function affected_rows() {
        return mysql_affected_rows();
    }

    /**
     * Ritorna il numero di righe ritornate dall'ultimo result set
     * 
     * @return int Numero di righe dell'ultimo result set
     */
    public function num_rows($result_set_index = -1) {
        return mysql_num_rows( ($result_set_index != -1 ) ? $this->result_set_array[$result_set_index] : $this->result_set);
    }


    /**
     * Esegue una query. Il result set corrispondente viene salvato nel buffer di result set, se non specificato diversamente.
     *
     * @param string $sql                   Costrutto sql da eseguire
     * @param boolean $print                Indica se stampare la query a video
     * @param boolean $temporary_result_set Indica se utilizzare un result set temporaneo, ovvero che non viene salvato nel buffer
     * @return int                          Indice interno del buffer dove è stato salvato il result set ottenuto
     */
    public function query($sql, $print = false, $temporary_result_set = false) {

        if(!$temporary_result_set) {
            $this->total_queries++;
        }

        $start_time = $this->get_microtime();

        if (!$this->result_set = mysql_query($sql))
            throw new Exception("SQL Error: ".mysql_error()."\n\n SQL: $sql");

        if(!$temporary_result_set) {
            $this->result_set_array[sizeof($this->result_set_array)] = $this->result_set;
        }

        $end_time = $this->get_microtime();

        $this->total_time_queries += $end_time - $start_time;

        if($print) {
            //TODO REFACTORING
            $search = array("from ","and ","where ");
            $replace = array("\nFROM ","\nAND ","\nWHERE ");

            //misura di sicurezza in mancanza di un'idea migliore
            //stampa solo in certi ip
            
                echo "<pre>----\n\n ".str_replace($search,$replace,$sql."\n\n").";\n</pre>";
        }

        if(!$temporary_result_set)
            return sizeof($this->result_set_array)-1;
        else
            return $this->result_set;


        // da decidere se usare
        //controllo se connessione attiva
        //        if (!$this->conn) {
        //            $this->connect();
        //        }

    }

    /**
     * Ottiene un record (riga di una tabella) dall'ultimo result set o da un result set specificato.
     *
     * @param int $result_set_index Indice del result set da cui si vuole recuperare il record; se non indicato utilizza l'ultimo result set disponibile
     * @return mixed                Record risultante come array, false altrimenti
     */
    public function get_record($result_set_index = -1) {

        if ($result_set_index == -1)
            $result_set_index = sizeof($this->result_set_array)-1;

        if (mysql_num_rows($this->result_set_array[$result_set_index]) == 0)
            return false;

        if ($record_array = mysql_fetch_assoc($this->result_set_array[$result_set_index]))
            return $record_array;
        else
            return false;
    }


    /**
     * Ottiene il primo valore di un record (scalare) dall'ultimo result set o da un result set specificato
     *
     * @param int $result_set_index Indice del result set da cui si vuole recuperare il record; se non indicato utilizza l'ultimo result set disponibile
     *
     * @return mixed                Valore risultante, false altrimenti
     */
    public function get_scalar($result_set_index = -1) {

        if ($result_set_index == -1)
            $result_set_index = sizeof($this->result_set_array)-1;

        if (mysql_num_rows($this->result_set_array[$result_set_index]) == 0)
            return false;

        if ($record_array = mysql_fetch_array($this->result_set_array[$result_set_index]))
            return $record_array[0];
        else
            return false;
    }

    /**
     * Ottiene il primo valore di un record eseguendo la query; è la combinazione di {@link query()} e {@link get_scalar()}.
     *
     * @param string $sql       Costrutto sql da eseguire
     * @param boolean $print    Indica se stampare la query a video
     *
     * @return mixed            Valore risultante, false altrimenti
     *
     * @see query()
     * @see get_scalar()
     *
     */
    public function query_scalar ($sql, $print = false) {
        $index = $this->query($sql, $print);
        return $this->get_scalar($index);
    }

    /**
     * Recupera il prossimo id disponibile per la tabella specificata. Attenzione: è il prossimo id di autoincrement, non è il max+1 di quelli presenti.
     *
     * @param string $table     Tabella di cui si vuole conoscere il prossimo id disponibile
     * @return int              Prossimo id disponibile
     */
    public function next_id($table) {
        $sql = "SHOW TABLE STATUS LIKE '$table'";

        if(!$result_set = mysql_query($sql))
            throw new Exception("SQL Error: ".mysql_error()."\n\n SQL: $sql");

        $row = mysql_fetch_array($result_set);

        $next_id = $row['Auto_increment'];

        return $next_id;
    }

    /**
     * Ritorna l'id inserito dall'ultima query di insert eseguita.
     *
     * @return int Ultimo id inserito, false altrimenti
     */
    public function insert_id() {
        $id = mysql_insert_id();
        if (!empty($id))
            return $id;
        else
            return false;
    }

    /**
     * Ritorna l'ultimo result set.
     *
     * @return resource Ultimo result set
     */
    public function last_result_set() {
        return $this->result_set;
    }

    /**
     * Elenca tutte le tabelle presenti nel database selezionato.
     *
     * @param boolean $print    Indica se stampare la query di recupero della lista tabelle
     * @return array            Array contenente la lista delle tabellee
     */
    public function list_tables ($print = false) {

        $tables = array();

        $sql = "SHOW FULL TABLES FROM ".$this->database_name." where table_type = 'base table'";

        $this->query($sql, false, $print);

        while ($record = $this->get_record()) {
            $record_seq = array_values($record);
            $tables[] = $record_seq[0];
        }

        return $tables;

    }

    /**
     * Ritorna il microtime attuale in un formato utilizzabile
     *
     * @return float    Microtime attuale
     */
    private function get_microtime() {
        list($usec, $sec) = explode(" ", microtime());
        return ((float)$usec + (float)$sec);
    }

    /**
     * Resetta l'indice del buffer di result set e l'ultimo result set.
     *
     */
    public function reset_index() {
        $this->result_set_array = array();
        $this->result_set = null;
    }

    /**
     * Riduce l'indice se la dimensione è troppo grande.
     */
    public function reduce_index() {

        if (sizeof($this->result_set_array) > 20)
            $this->result_set_array = array_slice($this->result_set_array, 10);
    }

    /**
     * Fornisce il numero di record che una certa query recupera (senza recuperarli o problemi di limit); sostituisce la parte di select con un count(0). Ci sono problemi noti con il group by.
     * 
     * @param string $sql   Costrutto sql di cui si vuole conoscere il numero di record senza eseguirlo
     * @return mixed        Valore risultante 
     */
    public function count($sql) {

        //occhio con i group by..

        $temp_sql = preg_replace('/select(.*)from/imsU', 'select count(0) from', $sql, 1, $conto); //solo e sempre il primo che trovo (evito di imemrdare le subquery)

        return $this->query_scalar($temp_sql);

    }

    /**
     * Ritorna lo username.
     * @return string Username
     */
    public function get_username () {
        return $this->username;
    }

}
